#include "dw_net.hpp"
#include "digital_world.hpp"

#include <iostream>
using namespace std;
using namespace dw;

enum class DWMsgType : uint32_t
{
    ClientAccept,
    PingServer,

    CreateAgent,
    
    POST_WorldState,
    POST_AgentState,
    POST_PathState,
    POST_MapState,

    GET_WorldState,
    GET_AgentState,
    GET_PathState,
    GET_MapState,
};

uint32_t dw::net::Scramble(uint32_t in) {
    return in;
}

class DigitalWorldServer :
    public dw::net::server_interface<DWMsgType>
{
    using message = dw::net::message<DWMsgType>;

private:
    World world;
    unordered_map<uint32_t, ID> client_agent_map;

private:
    ID getAgentID(uint32_t idx) const {
        auto iter = client_agent_map.find(idx);
        if (iter == client_agent_map.end()) return 0;
        return client_agent_map.at(idx);
    }

public:
    DigitalWorldServer(uint16_t nPort) :
        dw::net::server_interface<DWMsgType>(nPort),
        world("./test/MapData/perlin.txt")
    {
        shared_ptr<AutonomousAgent> agent1 = world.createAutonomousAgent();
        shared_ptr<AutonomousAgent> agent2 = world.createAutonomousAgent();
        shared_ptr<AutonomousAgent> agent3 = world.createAutonomousAgent();

        cout << world.getMap() << endl;

        // world.simplify(agent1->getID());
        // world.simplify(agent2->getID());
        // world.simplify(agent3->getID());

        // cout << world << std::endl;
    }

public:
    virtual void OnClientValidate(std::shared_ptr<dw::net::connection<DWMsgType>> client) override
    {
        std::cout << "[SERVER] Client [" << client->GetID() << "] Validated!" << std::endl;
        message msg(DWMsgType::ClientAccept);
        client->Send(msg);
    }

protected:
    virtual bool OnClientConnect(std::shared_ptr<dw::net::connection<DWMsgType>> client) override
    {
        return true;
    }

    virtual void OnClientDisconnect(std::shared_ptr<dw::net::connection<DWMsgType>> client) override
    {
        cout << "[SERVER] Removing client <" << client->GetID() << ">" << endl;

        ID id = getAgentID(client->GetID());

        if (id) 
        {
            client_agent_map.erase(client->GetID());
            world.deleteAgent(id);
        }
    }

    virtual void OnMessage(std::shared_ptr<dw::net::connection<DWMsgType>> client, dw::net::message<DWMsgType> &client_msg) override
    {
        switch (client_msg.type())
        {
            case DWMsgType::PingServer:
            {
                cout << "[" << client->GetID() << "]: Ping" << endl;
                // Simply Pong back
                client->Send(client_msg);
            }
            break;

            case DWMsgType::CreateAgent:
            {
                cout << "[" << client->GetID() << "]: Create Agent" << endl;
                message msg(DWMsgType::POST_AgentState);

                shared_ptr<Agent> agent = world.createAgent();
                client_agent_map.insert_or_assign(client->GetID(), agent->getID());

                msg.append(agent->encode());
                client->Send(msg);
            }
            break;

            case DWMsgType::POST_AgentState:
            {
                cout << "[" << client->GetID() << "]: Receive Agent State" << endl;
                std::string str_agent = client_msg.data();
                std::cout << str_agent << std::endl;

                Agent::State state;
                state.decode(str_agent);
                ID id = getAgentID(client->GetID());

                if (0 == id) {
                    client->Disconnect();
                } else if (id != state.id) {
                    client_agent_map.insert_or_assign(client->GetID(), id);
                } else {
                    world.updateAgentState(state, id);
                }
            }
            break;

            case DWMsgType::GET_WorldState:
            {
                cout << "[" << client->GetID() << "]: Request World State" << endl;
                world.Update();
                
                message msg(DWMsgType::POST_WorldState);
                msg.append(world.encode());
                client->Send(msg);
            }
            break;

            case DWMsgType::GET_MapState:
            {
                cout << "[" << client->GetID() << "]: Request Map State" << endl;

                message msg(DWMsgType::POST_MapState);
                msg.append(world.getMap().encode());
                client->Send(msg);
            }
            break;

            case DWMsgType::GET_PathState:
            {
                cout << "[" << client->GetID() << "]: Request Path" << endl;

                std::string str_agent = client_msg.data();
                std::cout << str_agent << std::endl;

                Agent::State state;
                state.decode(str_agent);

                message msg(DWMsgType::POST_PathState);

                shared_ptr<Path> p = world.FindPath(state);
                Coord pos = state.vPosition;
                while (nullptr == p)
                {
                    Coord destination = world.getMap().getSpawnPoint();
                    Coord distance = destination - pos;
                    while (distance.mag2() < 0.1)
                    {
                        destination = world.getMap().getSpawnPoint();
                        distance = destination - pos;
                    }

                    p = world.FindPath(state, destination);
                }
                world.simplify(p);
                p->interpolate(0.5);
                msg.append(p->encode());

                client->Send(msg);
            }
            break;

            default:
            {
                std::cout << "[" << client->GetID() << "]: Unknown Message Type: " << client_msg << std::endl;
            }
            break;
        }
    }
};

int main() {
    DigitalWorldServer server(60000);
    server.Start();

    while (1)
    {
        server.Update(-1, true);
    }

    return 0;
}

